/* Copyright © Worklight Ltd. 2006-2011.  All rights reserved. */

package com.worklight.wlclient.api;

import java.util.Set;

import org.apache.http.client.CookieStore;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;

import android.content.Context;

import com.worklight.wlclient.WLClientUtils;
import com.worklight.wlclient.WLConfig;
import com.worklight.wlclient.WLCookieManager;
import com.worklight.wlclient.WLRequest;
import com.worklight.wlclient.WLRequestListener;

/**
 * This is the starting point class of all the Java worklight Client API
 */

public class WLClient {
	private static final String INIT_REQUEST_PATH = "authentication";
	private static final String INVOKE_PROCEDURE_REQUEST_PATH = "query";
	private static final String LOG_ACTIVITY_REQUEST_PATH = "logactivity";
	public static final String KEY_TIME_OUT = "timeout";
	public static final String KEY_INVOKE_PROCEDURE_INVOCATION_CONTEXT = "invokcationContext";
	public static final String KEY_LISTENER = "listener";
	
	private boolean isInitialized;
	private WLAuthListener authListener;
	private HttpContext httpContext;
	private WLConfig config;
	
	/**
	 * Constructor
	 * @param context - the parent activity 
	 * @param authListener - Sets an authentication listener that WLClient can use for authentication related task. 
	 * This method must be called in order for WLClient to be able to access protected resources in the WorkLight server
	 */
	
	public WLClient(Context context, WLAuthListener authListener) {
		this.config = new WLConfig(context);
		this.authListener = authListener;
		this.httpContext = new BasicHttpContext();
	}
	
	public WLClient(Context context) {
		this(context, null);
	}
	
	/**
	 * @deprecated - use connect instead
	 * 
	 * Sends an 'init' request to the server, establishing server connection and application version validity.
	 * @param responseListener - The listener to invoke success/failure when init done
	 */
	
	public void init(WLResponseListener responseListener) {		 
		connect(responseListener);
	}
	
	public void connect(WLResponseListener responseListener) {		 
		if (WLCookieExtractor.cookies != null){
			updateCookiesFromWebView();
		}
		
		// Make a dummy request to the server to receive the cookies
		WLRequestOptions requestOptions = new WLRequestOptions();
		requestOptions.addParameter("action","test");
		requestOptions.setResponseListener(responseListener);
		
		InitRequestListener initRequestListener = new InitRequestListener (this);	
		WLRequest initRequest = new WLRequest(initRequestListener, httpContext, requestOptions, config);
		initRequest.makeRequest(INIT_REQUEST_PATH);
	}
	
	/**
	 * Invokes an adapter procedure (Similar to the JavaScript WL.Client.invokeProcedure). 
	 * This method is asynchronous; the response is returned to the supplied listener callback functions.
	 * @param invocationData - The invocation data parameters to send on the request, see WLProcedureInvocationData.
	 * @param responseListener - The listener that will handle the response when return from the server, @see {@link WLAuthListener}.
	 * @param requestOptions - optional setting and data map, 
	 *  Use userOptions for the following if needs:
	 * 		To change request timeout - use WLCLient.KEY_TIME_OUT key, the value must be Integer.
	 *  	To add your context object - use WLCLient.KEY_INVOKE_PROCEDURE_INVOCATION_CONTEXT key, the value can be any object.
	 */
	public void invokeProcedure (
			WLProcedureInvocationData invocationData, 
			WLResponseListener responseListener,  
			WLRequestOptions requestOptions) {
		
		if (!isInitialized) {
			throw new RuntimeException("WLClient not initialized");
		}

		if (requestOptions == null) {
			requestOptions = new WLRequestOptions();
		}
		requestOptions.setResponseListener(responseListener);
		
		// Build request options
		requestOptions.addParameters(invocationData.getInvocationDataMap());
		InvokeProcedureRequestListener invokeProcedureInternalListener = new InvokeProcedureRequestListener();

		WLRequest invokeProcedureWLRequest = new WLRequest(
			invokeProcedureInternalListener, 
			httpContext, 
			requestOptions, 
			config);
		
		invokeProcedureWLRequest.setAuthListener(getAuthListener());	
		invokeProcedureWLRequest.makeRequest(INVOKE_PROCEDURE_REQUEST_PATH);
	}
	
	/**
	 * Invokes an adapter procedure (Similar to the JavaScript WL.Client.invokeProcedure). 
	 * the response is returned to the supplied listener callback functions.
	 * Upon a succesful response from the server, the listener's onSuccess is called. onFailure is called in case of an error, 
	 * including a login form response from the server, which is also considered an error. however, if the WLAuthListener is set, WLClient calls the WLAuthListener handleLoginResponse in case a login response is returned from the server.
	 * @param invocationData - he invocation data parameters to send on the request.
	 * @param responseListener - The listener that will handle the response when return from the server (success or failure).
	 */
	public void invokeProcedure(
			WLProcedureInvocationData invocationData, 
			WLResponseListener responseListener) {
		
		invokeProcedure(invocationData, responseListener, null);
	}
	
	/**
	 * Logs a user activity in the server's activity tables. Data can be used for auditing or reporting purposes.
	 * @param activityType - the activity type.
	 */
	public void logActivity(String activityType) {
		if (!isInitialized) {
			throw new RuntimeException("WLClient not initialized");
		}
		
		if (activityType == null) {
			throw new RuntimeException("ActivityType cannot be null");
		}
		
		WLRequestOptions requestOptions = new WLRequestOptions();
		requestOptions.addParameter("activity", activityType);
		LogActivityListener logActivityListener = new LogActivityListener();
		
		WLRequest logActivityWLRequest = new WLRequest(
			logActivityListener, 
			httpContext, 
			requestOptions, 
			config);

		logActivityWLRequest.setAuthListener(getAuthListener());
		logActivityWLRequest.makeRequest(LOG_ACTIVITY_REQUEST_PATH);
	}
	
	protected void setInitialized(boolean isInitialized) {
		this.isInitialized = isInitialized;
	}

	protected WLAuthListener getAuthListener() {
		return authListener;
	}
	
	private void updateCookiesFromWebView() {		
		WLCookieManager.setCookies(WLCookieExtractor.cookies, config.getRootURL().getHost());
		Set<Cookie> cookies = WLCookieManager.getCookies(); 

		if (cookies != null) {
			CookieStore cookieStore = new BasicCookieStore();
			for (Cookie cookie : cookies){
				cookieStore.addCookie(cookie);
			}
			httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
		} else {
			WLClientUtils.debug("No Cookies found for url " + config.getRootURL().getHost() + " in WebView.");
		}
	}
}

//==============================Request Listeners classes=============================

/**
 * The init listener class, invoked when the init request is done
 */
class InitRequestListener implements WLRequestListener {
	
	private WLClient wlClient;

	public InitRequestListener(WLClient wlClient) {
		this.wlClient = wlClient;
	}
	
	@Override
	public void onFailure(WLFailResponse failResponse) {				
		failResponse.getOptions().getResponseListener().onFailure(failResponse);
	}

	@Override
	public void onSuccess(WLResponse response) {
		wlClient.setInitialized(true);
		response.getOptions().getResponseListener().onSuccess(response);
	}
	
}
 
/**
 * The init listener class, invoked when the init request is done
 */
class LogActivityListener implements WLRequestListener {
	@Override
	public void onFailure(WLFailResponse failResponse) {
		WLClientUtils.error("logActivity failure. Response from server is " + failResponse);
	}

	@Override
	public void onSuccess(WLResponse response) {
		WLClientUtils.debug("logActivity success. Response from server is " + response.toString());
	}	
}

/**
 * The invoke procedure listener class, invoked when the invoke procedure request is done
 */
class InvokeProcedureRequestListener implements WLRequestListener {
	
	@Override
	public void onFailure(WLFailResponse failResponse) {
		WLProcedureInvocationFailResponse pifResponse = new WLProcedureInvocationFailResponse(failResponse);		
		failResponse.getOptions().getResponseListener().onFailure(pifResponse);
	}

	@Override
	public void onSuccess(WLResponse response) {
		try {
			WLProcedureInvocationResult piResponse = new WLProcedureInvocationResult(response);			
			if (piResponse.isSuccessful()){
				piResponse.getOptions().getResponseListener().onSuccess(piResponse);
			}
			else {				
				WLProcedureInvocationFailResponse pifResponse = new WLProcedureInvocationFailResponse(response); 				 
				pifResponse.setErrorCode(WLErrorCode.PROCEDURE_ERROR);							
				pifResponse.getOptions().getResponseListener().onFailure(pifResponse);				
			} 
		} 
		catch (Exception e) {
			// TODO - check for more specific exception
			WLProcedureInvocationFailResponse pifResponse = new WLProcedureInvocationFailResponse(response);
			pifResponse.setErrorCode(WLErrorCode.UNEXPECTED_ERROR);
			pifResponse.setErrorMsg(String.format("Error processing response. Error is '%s'.", e.getMessage()));			
			pifResponse.getOptions().getResponseListener().onFailure(pifResponse);			
		}
	}
}